package com.fub.view;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PaintFlagsDrawFilter;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.fub.R;
import com.fub.Start;
import com.fub.guidance.Building;
import com.fub.guidance.Graph;
import com.fub.guidance.Point;
import com.fub.guidance.Relation;
import com.fub.guidance.Vertex;
import com.fub.guidance.Wall;
import com.fub.positioning.FingerprintService;
import com.fub.positioning.MapMatcher;
import com.fub.positioning.Measurement;
import com.fub.positioning.PositioningUtil;
import com.fub.positioning.ReferencePoint;
import com.fub.positioning.WifiRecorder;
import com.fub.util.PreferenceDAO;
import com.fub.util.Util;
import com.fub.view.mapcomponents.BigLabelComponent;
import com.fub.view.mapcomponents.GraphComponent;
import com.fub.view.mapcomponents.ImageLabelComponent;
import com.fub.view.mapcomponents.RelationComponent;
import com.fub.view.mapcomponents.RouteComponent;
import com.fub.view.mapcomponents.SmallLabelComponent;
import com.fub.view.mapcomponents.WallComponent;

public class MapView extends View {

	private static final int BACKGROUND_COLOR = Color.argb(0, 0, 0, 0);
	private FingerprintService fingerprintManager;
	private Graph graph;
	private Building building;
	private float previousPositionX;
	private float previousPositionY;
	private float moveX;
	private float moveY;
	private boolean mapMoved;
	private float mapMoveDelta;
	private int oldLevel;
	private float oldZoom;
	private PreferenceDAO preferenceDao;
	private Point newReferencePointPosition;
	private Bitmap expBitmap;
	private Bitmap personBitmap;
	private Bitmap entranceBitmap;
	private Bitmap stairsBitmap;
	private Bitmap liftBitmap;
	private Bitmap rpBitmap;
	private Canvas fullCanvas;
	private List<SmallLabelComponent> labelsOnDisplay;
	private List<ImageLabelComponent> referencePointsLabels;
	private List<WallComponent> wallsOnDisplay;
	private List<RelationComponent> relationsOnDisplay;
	private RelationComponent outerWallRelation;
	private RouteComponent route;
	private GraphComponent graphComponent;
	private boolean graphEnabled;
	private boolean labelsEnabled;
	private boolean routeEnabled;
	private boolean settingChanged;
	private boolean fingerprintEnabled;
	private String oldSelectedNode;
	private Paint drawPaint;
	private Paint paintKNNPosition;
	private Paint paintKNNLines;
	private Paint paintKWNNPosition;
	private Paint paintKWNNLines;
	private Paint paintMatchedPoint;
	private Paint paintMatchedLines;
	private ProgressDialog progressDialog;
	private Handler handler;
	private PaintFlagsDrawFilter setfil;
	private Matrix matrix;
	private MapMatcher mapMatcher;
	ImageLabeFactory imageLabeFactory;

	public MapView(Context context, AttributeSet set) {
		super(context);
		init();
	}

	public MapView(Context context) {
		super(context);
		init();
	}

	private void init() {
		setFocusable(true);
		this.graph = Graph.getInstance();
		this.building = Building.getInstance();
		this.preferenceDao = new PreferenceDAO(getContext());
		this.setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG);
		this.matrix = new Matrix();
		this.labelsOnDisplay = new ArrayList<SmallLabelComponent>();
		this.referencePointsLabels = new ArrayList<ImageLabelComponent>();
		this.handler = new Handler();
		this.mapMatcher = new MapMatcher();
		this.imageLabeFactory = new ImageLabeFactory();
		// bitmaps
		this.expBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.pfeil);
		this.personBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.person);
		this.entranceBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.entry_kl);
		this.stairsBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.stairs_kl);
		this.liftBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.lift_kl);
		this.rpBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.rp);

		// init main canvas
		if (Start.fullBitmap == null) {
			Start.fullBitmap = Bitmap.createBitmap(
					(int) this.building.getMaxX() + 500,
					(int) this.building.getMaxY() + 500, Config.ARGB_4444);
		}
		this.fullCanvas = new Canvas(Start.fullBitmap);
		this.fingerprintManager = FingerprintService.getInstance(getContext());
		if (this.preferenceDao.isFingerprintEnabled()) {
			this.fingerprintManager.startMeasuringThread();
			this.fingerprintManager.enableMeasuringThread();
		}

		// init paints
		this.paintKNNPosition = new Paint();
		this.paintKNNPosition.setStyle(Style.FILL);
		this.paintKNNPosition.setColor(Color.RED);
		this.paintKNNLines = new Paint();
		this.paintKNNLines.setStyle(Style.STROKE);
		this.paintKNNLines.setColor(Color.RED);

		this.paintKWNNPosition = new Paint();
		this.paintKWNNPosition.setStyle(Style.FILL);
		this.paintKWNNPosition.setColor(Color.BLUE);
		this.paintKWNNLines = new Paint();
		this.paintKWNNLines.setStyle(Style.STROKE);
		this.paintKWNNLines.setColor(Color.BLUE);

		this.paintMatchedLines = new Paint();
		this.paintMatchedLines.setStyle(Style.STROKE);
		this.paintMatchedLines.setColor(Color.GRAY);

		this.paintMatchedPoint = new Paint();
		this.paintMatchedPoint.setStyle(Style.FILL);
		this.paintMatchedPoint.setColor(Color.YELLOW);
		startRedrawThread();
	}

	private void startRedrawThread() {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				while (!MapView.this.fingerprintManager.isStopThread()) {
					postInvalidate();
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						Log.e("ERROR", String.valueOf(e));
					}
				}
			}
		};
		new Thread(runnable).start();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(Color.LTGRAY);
		this.settingChanged = false;
		if (hasSettingChanged() || this.preferenceDao.isFingerprintEnabled()) {
			this.settingChanged = true;
			Start.fullBitmap.eraseColor(BACKGROUND_COLOR);
			updateOldSettingsToPreferenDaoValues();
			setEtage();
		}
		// draw relations
		if (this.settingChanged && !this.preferenceDao.isFingerprintEnabled()) {
			drawRelations();
		}
		// draw shortest path
		if (this.preferenceDao.isRoutingEnabled() && this.settingChanged) {
			drawShortestPath();
		}
		// draw walls
		if (this.settingChanged) {
			drawWalls();
		}
		// draw routing graph
		if (this.preferenceDao.isShowGraphEnabled() && this.settingChanged) {
			this.graphComponent = new GraphComponent(this.preferenceDao);
			this.graphComponent.draw(this.fullCanvas);
		}
		// draw room labels
		if (this.preferenceDao.isShowRoomLabels()
				&& this.preferenceDao.getZoomFactor() >= 0.5
				&& this.settingChanged
				&& !this.preferenceDao.isFingerprintEnabled()) {
			drawRoomLabels();
		}
		// draw reference points
		if (this.preferenceDao.isFingerprintEnabled()) {
			 drawReferencePoints(this.fullCanvas);
		}
		calculateMatrix();
		canvas.setDrawFilter(this.setfil);
		canvas.drawBitmap(Start.fullBitmap, this.matrix, this.drawPaint);
	}

	private boolean hasSettingChanged() {
		boolean changed = false;
		changed |= (this.oldLevel != this.preferenceDao.getCurrentEtage());
		changed |= (this.oldZoom != this.preferenceDao.getZoomFactor());
		changed |= (this.routeEnabled != this.preferenceDao.isRoutingEnabled());
		changed |= (this.graphEnabled != this.preferenceDao
				.isShowGraphEnabled());
		changed |= (this.labelsEnabled != this.preferenceDao.isShowRoomLabels());
		changed |= (this.fingerprintEnabled != this.preferenceDao
				.isFingerprintEnabled());
		if (this.oldSelectedNode != null)
			changed |= (this.oldSelectedNode != this.preferenceDao
					.getSelectedNodeId());
		return changed;
	}

	private void updateOldSettingsToPreferenDaoValues() {
		this.oldLevel = this.preferenceDao.getCurrentEtage();
		this.oldZoom = this.preferenceDao.getZoomFactor();
		this.routeEnabled = this.preferenceDao.isRoutingEnabled();
		this.graphEnabled = this.preferenceDao.isShowGraphEnabled();
		this.labelsEnabled = this.preferenceDao.isShowRoomLabels();
		this.oldSelectedNode = this.preferenceDao.getSelectedNodeId();
		this.fingerprintEnabled = this.preferenceDao.isFingerprintEnabled();
	}

	private void calculateMatrix() {
		this.matrix.reset();
		this.matrix.postTranslate(-this.preferenceDao.getPosition().getX(),
				-this.preferenceDao.getPosition().getY());
		this.matrix.postTranslate(this.preferenceDao.getPosition().getX(),
				this.preferenceDao.getPosition().getY());
		this.matrix.postTranslate(-this.preferenceDao.getLeftUpperCorner()
				.getX(), -this.preferenceDao.getLeftUpperCorner().getY());
	}

	synchronized private void drawReferencePoints(Canvas canvas) {
		this.referencePointsLabels.clear();
		WifiRecorder recorder = WifiRecorder.getInstance(getContext());

		// add labels for all reference points
		if (!recorder.isPlayingSavedMeasurings()) {
			Log.d("Map", "Anzahl der NNs in FP Manager: "
					+ this.fingerprintManager.getNearestNeigbours().length);
			for (int i = 0; i < this.fingerprintManager.getNearestNeigbours().length; i++) {
				ReferencePoint referencePoint = this.fingerprintManager
						.getNearestNeigbours()[i];
				int red = 255 / (i + 1);
				int alpha = 0;
				int color = Color.argb(alpha, red, 0, 0);
				ImageLabelComponent rpLabel = new ImageLabelComponent(
						referencePoint.getPosition(), this.rpBitmap,
						this.preferenceDao, color,
						(int) referencePoint.getDistanceToCurrentPosition()
								+ "");
				this.referencePointsLabels.add(rpLabel);
			}
		}

		// draw lines beetween the KNNs
		if (this.fingerprintManager.getNearestNeigbours().length >= FingerprintService.K) {
			Point zoomedKWnnPosition = Util.calculateZoomedPosition(
					this.fingerprintManager.getKWeightedNearestNeighbour(),
					this.preferenceDao);
			if (!recorder.isPlayingSavedMeasurings())
				for (int i = 0; i < FingerprintService.K; i++) {
					Point positionKReferencePoint = this.fingerprintManager
							.getNearestNeigbours()[i].getPosition();
					Point zoomedPositionKReferencePoint = Util
							.calculateZoomedPosition(positionKReferencePoint,
									this.preferenceDao);
					this.fullCanvas.drawLine(
							zoomedPositionKReferencePoint.getX(),
							zoomedPositionKReferencePoint.getY(),
							zoomedKWnnPosition.getX(),
							zoomedKWnnPosition.getY(), this.paintKWNNLines);
				}

			Point zoomedMatchedPos = this.mapMatcher.getMatchedZoomedPosition(
					this.fingerprintManager.getKWeightedNearestNeighbour(),
					this.graph, this.preferenceDao);
			// draw matched point
			this.fullCanvas.drawCircle(zoomedMatchedPos.getX(),
					zoomedMatchedPos.getY(), 3, this.paintMatchedPoint);
			// draw the tangent
			this.fullCanvas.drawLine(zoomedMatchedPos.getX(),
					zoomedMatchedPos.getY(), zoomedKWnnPosition.getX(),
					zoomedKWnnPosition.getY(), this.paintMatchedPoint);
			// draw line between last measurement and current
			this.fullCanvas.drawLine(zoomedMatchedPos.getX(), zoomedMatchedPos
					.getY(), this.mapMatcher.getLastMeasuredZoomedPosition()
					.getX(), this.mapMatcher.getLastMeasuredZoomedPosition()
					.getY(), this.paintMatchedPoint);
			// draw the intersection of the tangent
			this.fullCanvas.drawCircle(zoomedKWnnPosition.getX(),
					zoomedKWnnPosition.getY(), 3, this.paintKWNNPosition);
		}
		for (ImageLabelComponent rpLabel : this.referencePointsLabels) {
			rpLabel.draw(this.fullCanvas);
		}
	}

	private void setEtage() {
		this.preferenceDao.setEtage(this.preferenceDao.getCurrentEtage());
	}

	private void drawShortestPath() {
		if (this.preferenceDao.getRoutingStart() == PreferenceDAO.NO_NODE_SET
				|| this.preferenceDao.getRoutingStart() == PreferenceDAO.NO_NODE_SET) {
		} else {
			if (hasRouteChanged()) {
				this.route = new RouteComponent(
						this.preferenceDao.getRoutingStart(),
						this.preferenceDao.getRoutingEnd(), this.preferenceDao);
			}
			this.route.draw(this.fullCanvas);
		}
	}

	private boolean hasRouteChanged() {
		if (this.route == null)
			return true;
		if (this.route.getSourceId() == this.preferenceDao.getRoutingStart()
				&& this.route.getDestinationId() == this.preferenceDao
						.getRoutingEnd()) {
			return false;
		} else {
			return true;
		}
	}

	private void drawRelations() {
		if (this.relationsOnDisplay == null || this.settingChanged) {
			this.relationsOnDisplay = new ArrayList<RelationComponent>();
			for (Relation relation : this.graph.getRelations()) {
				if (relation.getType().equals(
						Relation.BUILDING_WALL_RELATION_NAME)) {
					RelationComponent relationComponent = new RelationComponent(
							relation, this.preferenceDao, Color.argb(180, 255,
									255, 255));
					this.outerWallRelation = relationComponent;
				} else if (relation.getType().equals(
						Relation.COURT_RELATION_NAME)) {
					RelationComponent relationComponent = new RelationComponent(
							relation, this.preferenceDao, Color.LTGRAY);
					this.relationsOnDisplay.add(relationComponent);
				} else if (relation.getType().equals(
						Relation.INTERIOR_RELATION_NAME)) {
					String nodeId = relation.getNodes().get(0);
					if (this.graph.getNodeById(nodeId).getPosition().getZ() == this.preferenceDao
							.getCurrentEtage()) {
						RelationComponent relationComponent = new RelationComponent(
								relation, this.preferenceDao, Color.rgb(228,
										234, 217));
						this.relationsOnDisplay.add(relationComponent);
					}
				}
			}
		}
		if (this.outerWallRelation != null)
			this.outerWallRelation.draw(this.fullCanvas);
		for (RelationComponent relationComponent : this.relationsOnDisplay) {
			relationComponent.draw(this.fullCanvas);
		}
	}

	private void drawWalls() {
		if (this.wallsOnDisplay == null) {
			this.wallsOnDisplay = new ArrayList<WallComponent>();
			for (Wall wall : this.building.getWalls()) {
				WallComponent wallComponent = new WallComponent(wall,
						this.preferenceDao);
				this.wallsOnDisplay.add(wallComponent);
			}
		}
		if (this.settingChanged) {
			for (WallComponent wall : this.wallsOnDisplay) {
				if (wall.getWall().getZ() == this.preferenceDao
						.getCurrentEtage()) {
					wall.draw(this.fullCanvas);
				}
			}
		}
	}

	private void drawRoomLabels() {
		Collection<Vertex> nodes = this.graph.getNodes();
		this.labelsOnDisplay.clear();
		for (Vertex node : nodes) {
			int color = Color.argb(175, 0, 0, 0);
			if (node.getId().equals(this.preferenceDao.getRoutingStart())
					|| node.getId() == this.preferenceDao.getSelectedNodeId()) {
				color = Color.argb(255, 109, 38, 38);
			}
			if (node.getPosition().getZ() == this.preferenceDao
					.getCurrentEtage()) {
				drawSuitableLabel(node, color);
			}
		}
	}

	private void drawSuitableLabel(Vertex node, int color) {
		if (Util.isRoomType(node)) {
			if (node.getId() == this.preferenceDao.getSelectedNodeId()
					|| node.getId()
							.equals(this.preferenceDao.getRoutingStart())) {
				BigLabelComponent label = new BigLabelComponent(node,
						this.personBitmap, this.expBitmap, this.preferenceDao,
						color);
				label.draw(this.fullCanvas);
			} else {
				SmallLabelComponent smalllabel = new SmallLabelComponent(node,
						this.expBitmap, this.preferenceDao, color);
				smalllabel.draw(this.fullCanvas);
				this.labelsOnDisplay.add(smalllabel);
			}
		} else if (Util.isEntranceType(node)) {
			color = Color.argb(255, 100, 65, 65);
			ImageLabelComponent entranceLabel = this.imageLabeFactory
					.getImageLabelComponent(node.getPosition(),
							this.entranceBitmap, this.preferenceDao, color, "");

			entranceLabel.draw(this.fullCanvas);
		} else if (Util.isStairsType(node)) {
			ImageLabelComponent entranceLabel = this.imageLabeFactory
					.getImageLabelComponent(node.getPosition(),
							this.stairsBitmap, this.preferenceDao, color, "");
			entranceLabel.draw(this.fullCanvas);
		} else if (Util.isLiftType(node)) {
			ImageLabelComponent entranceLabel = this.imageLabeFactory
					.getImageLabelComponent(node.getPosition(),
							this.liftBitmap, this.preferenceDao, color, "");
			entranceLabel.draw(this.fullCanvas);
		}
	}

	class ImageLabeFactory {
		Map<Point, ImageLabelComponent> imageLabelMap;

		public ImageLabeFactory() {
			this.imageLabelMap = new HashMap<Point, ImageLabelComponent>();
		}

		public ImageLabelComponent getImageLabelComponent(Point position,
				Bitmap icon, PreferenceDAO preferenceDAO, int color, String text) {
			if (!this.imageLabelMap.containsKey(position)) {
				ImageLabelComponent label = new ImageLabelComponent(position,
						icon, MapView.this.preferenceDao, color, "");
				this.imageLabelMap.put(position, label);
			}
			return this.imageLabelMap.get(position);
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		event.setLocation((int) event.getX(), (int) event.getY());
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			this.previousPositionX = event.getX();
			this.previousPositionY = event.getY();
			this.mapMoved = false;
			return true;
		} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
			this.moveX = event.getX() - this.previousPositionX;
			this.moveY = event.getY() - this.previousPositionY;
			if (!this.mapMoved) {
				if (Math.abs(this.moveX) > this.mapMoveDelta
						|| Math.abs(this.moveY) > this.mapMoveDelta) {
					this.mapMoved = true;
				} else {
					return true;
				}
			}
			Point newPosition = new Point(this.preferenceDao.getPosition()
					.getX() - (int) this.moveX, this.preferenceDao
					.getPosition().getY() - (int) this.moveY,
					this.preferenceDao.getCurrentEtage());
			this.preferenceDao.setPosition(newPosition);
			this.previousPositionX = event.getX();
			this.previousPositionY = event.getY();
			invalidate();
			return true;
		} else {
			Point eventPosition = new Point(Math.round(event.getX()),
					Math.round(event.getY()),
					this.preferenceDao.getCurrentEtage());

			int posX = (int) ((this.preferenceDao.getLeftUpperCorner().getX() + event
					.getX()) / this.preferenceDao.getZoomFactor());
			int posY = (int) ((this.preferenceDao.getLeftUpperCorner().getY() + event
					.getY()) / this.preferenceDao.getZoomFactor());

			if (this.fingerprintManager.isMeasureDistance) {
				Point posKWNN = this.fingerprintManager
						.getKWeightedNearestNeighbour();
				Point posKNN = this.fingerprintManager.getKNearestNeighbour();

				float distanzeKNN = Util.euclidianDistance(new Point(posX,
						posY, 0), posKNN);
				float distanzeKWNN = Util.euclidianDistance(new Point(posX,
						posY, 0), posKWNN);

				try {
					String response = String.format(
							"Pos %s  KNN  %f KWNN %f MatchedKWNN %f saved",
							posX + "," + posY, distanzeKNN / 148,
							distanzeKWNN / 148);
					Toast.makeText(getContext(), response, Toast.LENGTH_LONG)
							.show();
					FileWriter testwriter = new FileWriter("/sdcard/test.txt",
							true);
					BufferedWriter out = new BufferedWriter(testwriter);
					out.write(response);
					out.newLine();
					out.close();
				} catch (IOException e) {
					final Toast hinweis1 = Toast.makeText(getContext(),
							"Kein Zugriff auf " + e.getMessage(),
							Toast.LENGTH_SHORT);
					hinweis1.show();
				}
				this.fingerprintManager.isMeasureDistance = false;
			}

			if (this.preferenceDao.isAddFingerprintEnabled()) {
				int posZ = this.preferenceDao.getCurrentEtage();
				this.newReferencePointPosition = new Point(posX, posY, posZ);
				showProcessDialog();
				new Thread(new Runnable() {
					@Override
					public void run() {
						MapView.this.fingerprintManager.addReferencePoint(
								MapView.this.preferenceDao.getBuildingId(),
								MapView.this.newReferencePointPosition);
						MapView.this.handler
								.post(MapView.this.hideProcessDialogAndInvalidate);
					}
				}).start();
				this.preferenceDao.disableAddFingerprintMode();
			}

			for (SmallLabelComponent label : this.labelsOnDisplay) {
				if (label.isTapped(eventPosition)) {
					this.preferenceDao.setSelectedNodeId(label.getNode()
							.getId());
					getRootView().findViewById(R.id.btnDetails).setVisibility(
							View.VISIBLE);
					invalidate();
				}
			}

			for (ImageLabelComponent rpLabel : this.referencePointsLabels) {
				if (rpLabel.isTapped(eventPosition)) {

					if (this.preferenceDao.isDeleteFingerprintEnabled()) {
						this.fingerprintManager.removeReferencePoint(
								this.preferenceDao.getBuildingId(),
								rpLabel.getPositon());
						this.preferenceDao.disableDeleteFingerprintMode();
						invalidate();
					} else if (this.fingerprintManager.isMeasureRP()) {
						startCompareActivity(PositioningUtil
								.createLocationLabel(
										this.preferenceDao.getBuildingId(),
										rpLabel.getPositon()));
					} else {
						String rpLocation = PositioningUtil
								.createLocationLabel(
										this.preferenceDao.getBuildingId(),
										rpLabel.getPositon());
						Measurement measurement = this.fingerprintManager
								.getReferencePoint(rpLocation).getMeasurement();
						Toast.makeText(getContext(), measurement.toString(),
								Toast.LENGTH_LONG).show();
					}
				}
			}
		}
		return false;
	}

	private void startCompareActivity(String location) {
		Bundle bundle = new Bundle();
		bundle.putString("location", location);
		Intent rpDetailIntent = new Intent(this.getContext(),
				RPListActivity.class);
		rpDetailIntent.putExtras(bundle);
		getContext().startActivity(rpDetailIntent);
	}

	private Runnable hideProcessDialogAndInvalidate = new Runnable() {
		@Override
		public void run() {
			MapView.this.progressDialog.hide();
			invalidate();
		}
	};

	private void showProcessDialog() {
		this.progressDialog = ProgressDialog.show(getContext(), "",
				getResources().getString(R.string.dialogGetReferencePoint),
				true);
		this.progressDialog.show();
	}
}
